<template>
  <div class="container">
    <div class="canvasWrap">
        <canvas
          id="canvas"
          :width="canvasInfo.width"
          :height="canvasInfo.height"
          @drop.prevent="handleMouseDrop($event)"
          @dragover.prevent="dragover"
        >
          您的浏览器不支持canvas
        </canvas>
    </div>
    <div class="materialWrap">
      <div class="materialWrap_up">
        <div class="baseTitle">系统组件…</div>
        <div class="materialWrap_up_list">
          <div class="materialWrap_up_list_item"
               draggable="true"
               @dragstart="handleItemDragStart($event)"
               @dragend="handleItemDragend($event)"
               data-type="box"
          ></div>
          <span>主变</span>
          <div class="materialWrap_up_list_item"
               draggable="true"
               @dragstart="handleItemDragStart($event)"
               @dragend="handleItemDragend($event)"
               data-type="cabinet"
          ></div>
          <span>箱变</span>
        </div>
        <el-button @click="showList">数据</el-button>
      </div>
      <div class="materialWrap_down">
        <div class="baseTitle">组件属性</div>
        标题
        <el-input v-model="itemObj.title"></el-input>
      </div>
    </div>
    <div class="dialogs">
      <div class="contextmenu">
        <div class="contextmenu_item" @click="handleDeleteItem">删除</div>
        <div class="contextmenu_item" @click="handleSettingItem">编辑</div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { computed, onMounted, reactive, ref, shallowRef, watch } from "vue";
import canvasProcessFun from "./hooks/canvasProcess";
import { ElMessage } from "element-plus";
//canvas画布属性
const canvasInfo = reactive({
  // width:window.innerWidth - 292,//画布宽度 = 浏览器视口宽度 - 属性栏宽度 - 纵向滚动条的宽度
  width:window.innerWidth - 572,//画布宽度 = 浏览器视口宽度 - 属性栏宽度 - 纵向滚动条的宽度
  height:window.innerHeight - 12,//画布高度 = 浏览器视口宽度 - 横向滚动条的高度
  scaleCount:1,//画布缩放比例
  scaleStep:.1,//缩放进度
  preScale:1,//上次缩放比例
  maxScale:5,//画布最大放大比例
  minScale:.5,//画布最小缩小比例
  mouseWheelX:0,//滚动滚轮时鼠标距离canvas左侧距离
  mouseWheelY:0,//滚动滚轮时鼠标距离canvas上侧距离
  originX:0,//canvas起始原点x坐标
  originY:0,//canvas起始原点y坐标
  mouseDownX:0,//鼠标按下时指针距离屏幕左侧距离
  mouseDownY:0,//鼠标按下时指针距离屏幕上侧距离
  canvasOffsetX:0,//canvas移动x距离
  canvasOffsetY:0,//canvas移动y距离
  canvasCurOffsetX:0,//canvas上次移动x距离
  canvasCurOffsetY:0,//canvas上次移动y距离
  status:0,//0-起始状态，1-画布移动状态，2-元素移动状态，3-添加连线
})
const ctx = shallowRef(null)
//canvas画布大小
const canvasWidth = computed(() => {
  return canvasInfo.width + 'px'
})
const canvasHeight = computed(() => {
  return canvasInfo.height + 'px'
})
//右键弹窗位置信息
const contextMenuLeft = ref('99999px')
const contextMenuTop = ref('99999px')
//鼠标点击选中的对象：邮件弹窗，左键按住拖动
const mouseKeyObjectIndex = ref(null)
//拖拽画布元素之前元素原先所在位置
const handleItemBeforeMoveX = ref(0)
const handleItemBeforeMoveY = ref(0)
//新增连接线Id- 满足画线条件后赋值新增id，鼠标在画布移动时匹配元素设置结束位置，鼠标弹起后编辑连线属性插入画布
const createLineId = ref(null)
//屏蔽浏览器自身右键菜单事件
document.oncontextmenu = (e) => {
  e.preventDefault()
}

onMounted(() => {
  const canvas = document.getElementById('canvas')
  ctx.value = canvas.getContext('2d')
  // 放大缩小
  canvas.addEventListener('mousewheel', e => {
    e.preventDefault()
    canvasInfo.mouseWheelX = e.offsetX
    canvasInfo.mouseWheelY = e.offsetY
    if(e.deltaY < 0){
      // console.log('上翻');
      if(canvasInfo.scaleCount < canvasInfo.maxScale){
        canvasInfo.scaleCount = parseFloat((canvasInfo.scaleCount + canvasInfo.scaleStep).toFixed(2))
      }else{
        return
      }
    }else{
      // console.log('下翻');
      if(canvasInfo.scaleCount > canvasInfo.minScale){
        canvasInfo.scaleCount = parseFloat((canvasInfo.scaleCount - canvasInfo.scaleStep).toFixed(2))
      }else{
        return
      }
    }
    canvasInfo.canvasOffsetX = canvasInfo.mouseWheelX - ((canvasInfo.mouseWheelX - canvasInfo.canvasOffsetX) * canvasInfo.scaleCount) / canvasInfo.preScale;
    canvasInfo.canvasOffsetY = canvasInfo.mouseWheelY - ((canvasInfo.mouseWheelY - canvasInfo.canvasOffsetY) * canvasInfo.scaleCount) / canvasInfo.preScale;
    // ctx.translate(canvasInfo.canvasOffsetX , canvasInfo.canvasOffsetY)
    // ctx.scale(canvasInfo.scaleCount, canvasInfo.scaleCount);
    renderCanvas()
    canvasInfo.preScale = canvasInfo.scaleCount;
    canvasInfo.canvasCurOffsetX  = canvasInfo.canvasOffsetX;
    canvasInfo.canvasCurOffsetY  = canvasInfo.canvasOffsetY;
  })
  //拖拽移动
  canvas.addEventListener('mousedown', e => {
    handleResetRightKeyActive()
    if(e.button === 0){
      console.log('触发鼠标左键事件，判断点击位置是否处于元素中，修改画布当前所属状态1-移动画布，2-移动元素，3-开始绘制连线');
      const mx = (e.offsetX - canvasInfo.canvasOffsetX) / canvasInfo.scaleCount
      const my = (e.offsetY - canvasInfo.canvasOffsetY) / canvasInfo.scaleCount
      //判断点击位置是否位于元素锚点内
      if(pointList.value.some(item => Math.sqrt(((mx - item.x)**2 + (my - item.y)**2)) <= 3)){
        console.log('在锚点上，开启连线绘制');
        canvasInfo.status = 3
        const handleClickPoints = pointList.value.find(item => Math.sqrt(((mx - item.x)**2 + (my - item.y)**2)) <= 3)
        if(handleClickPoints.type === 'IN'){
          ElMessage.error('连线只能从元素底部锚点开始')
          canvasInfo.status = 0
        }else{
          //给连接线设置开始点位置
          // const index = deviceList.value.findIndex(i => i.id === handleClickPoints.parentId)
          createLineId.value = createNewId()
          const lineObject = {
            id:createLineId.value,
            type:'line',//连接线
            beginPointsParentId:handleClickPoints.parentId,//记录起始点对所在父元素--动态关联，便于元素拖拽时重新绘制连线
            beginPointsIndex:handleClickPoints.index,//记录起始点对所在父元素上的第几个锚点
            endPointsParentId: null,//记录结束点对所在父元素--动态关联，便于元素拖拽时重新绘制连线
            endPointsIndex: null,//记录结束点对所在父元素上的第几个锚点
            endPointsX:null,//用于鼠标滑动时临时绘制连线
            endPointsY:null,
            headerPoint:handleClickPoints.headerPoint,
            footerPoint:handleClickPoints.footerPoint,
            extData:{
              title:'连接线'
            }
          }
          deviceList.value.push(lineObject)
        }
      }else{
        const arr = deviceList.value.filter( d => {
          //点击位置在元素内且不再锚点范围内
          return d.type !== 'line' && mx> d.ranges.x1 && mx < d.ranges.x2 && my > d.ranges.y1 && my < d.ranges.y2 && (pointList.value.some(item => Math.sqrt(((mx - item.x)**2 + (my - item.y)**2)) > 3))
        })
        //记录鼠标落下时所处页面位置
        canvasInfo.mouseDownX = e.x
        canvasInfo.mouseDownY = e.y
        if(arr.length){
          console.log('在元素内部');
          const id = arr[arr.length - 1].id
          mouseKeyObjectIndex.value = deviceList.value.findIndex(d => d.id === id)
          handleItemBeforeMoveX.value = deviceList.value[mouseKeyObjectIndex.value].x
          handleItemBeforeMoveY.value = deviceList.value[mouseKeyObjectIndex.value].y

          //生成动态辅助线
          const obj = {
            type:'auxiliaryLine',
            beginX:-100000,
            beginY:handleItemBeforeMoveY.value,
            endX:canvasInfo.width + 100000,
            endY:handleItemBeforeMoveY.value
          }
          deviceList.value.push(obj)

          canvasInfo.status = 2
        }else{
          console.log('在画布上');
          canvasInfo.status = 1
        }
      }
    }else if(e.button === 2){
      editFlag.value = false
      console.log('触发鼠标右键事件，给画布中的元素绑定属性');
      console.log(e);
      const mx = Number(parseFloat(String((e.offsetX - canvasInfo.canvasOffsetX) / canvasInfo.scaleCount)).toFixed(0))
      const my = Number(parseFloat(String((e.offsetY - canvasInfo.canvasOffsetY) / canvasInfo.scaleCount)).toFixed(0))
      const arr = mouseRightKeyLocationInItem(mx,my,deviceList.value)
      if(arr.length){
        const id = arr[arr.length - 1].id
        const index = deviceList.value.findIndex(d => d.id === id)
        contextMenuLeft.value = e.x + 'px'
        contextMenuTop.value = e.y + 'px'
        mouseKeyObjectIndex.value = index
        deviceList.value[mouseKeyObjectIndex.value].active = true
      }
    }
    renderCanvas()//渲染被选中元素样式
  })

  //鼠标右击canvas，获取鼠标指针所在画布上的元素集合（矩形，连接线）
  const mouseRightKeyLocationInItem = (mx = 0, my = 0, list = []) => {
    const arr = []
    list.forEach(d => {
      if(d.type === 'rect' && mx> d.ranges.x1 && mx < d.ranges.x2 && my > d.ranges.y1 && my < d.ranges.y2){
        arr.push(d)
      }else if(
        d.type === 'line'
        &&
        d.ranges.some(d =>
          (Math.abs(mx - d.x1) < 2 && my >= d.y1 && my <= d.y2) //纵向比较
          ||
          (((mx >= d.x1 && mx <= d.x2) || (mx <= d.x1 && mx >= d.x2)) && (Math.abs(my - d.y2) < 2)) //横向比较，区分左右
        )
      ){
        arr.push(d)
      }
    })
    return arr
  }


  canvas.addEventListener('mousemove', e => {
    if(canvasInfo.status === 1){//移动画布
      canvasInfo.canvasOffsetX = canvasInfo.canvasCurOffsetX  + (e.x - canvasInfo.mouseDownX)
      canvasInfo.canvasOffsetY = canvasInfo.canvasCurOffsetY + (e.y - canvasInfo.mouseDownY)
      renderCanvas()
    }else if(canvasInfo.status === 2){//移动元素
      const ElMoveX = e.x - canvasInfo.mouseDownX
      const ElMoveY = e.y - canvasInfo.mouseDownY
      const item = deviceList.value[mouseKeyObjectIndex.value]
      item.x = Number(parseFloat(String(handleItemBeforeMoveX.value + ElMoveX /canvasInfo.scaleCount)).toFixed(0))
      item.y = Number(parseFloat(String(handleItemBeforeMoveY.value + ElMoveY /canvasInfo.scaleCount)).toFixed(0))

      //修改辅助线位置
      const auxiliaryLine = deviceList.value.find(d => d.type === 'auxiliaryLine')
      auxiliaryLine.beginY = item.y
      auxiliaryLine.endY = item.y

      item.points = calculatePoints(item.baseType,item.x,item.y,item.id)
      item.ranges = calculateRanges(item.baseType,item.x,item.y)
      renderCanvas()
    }else if(canvasInfo.status === 3){//绘制连接线
      console.log('绘制连接线');
      const x = Number(parseFloat(String((e.offsetX - canvasInfo.canvasOffsetX) / canvasInfo.scaleCount)).toFixed(2))
      const y = Number(parseFloat(String((e.offsetY - canvasInfo.canvasOffsetY) / canvasInfo.scaleCount)).toFixed(2))
      const drawLine = deviceList.value.find(d => d.id === createLineId.value)
      drawLine.endPointsX = x
      drawLine.endPointsY = y
      renderCanvas()
    }
  })
  canvas.addEventListener('mouseup', (e) => {
    if(canvasInfo.status === 1){//画布移动结束
      canvasInfo.canvasCurOffsetX = canvasInfo.canvasOffsetX
      canvasInfo.canvasCurOffsetY = canvasInfo.canvasOffsetY
    }else if(canvasInfo.status === 2){//元素移动结束
      //删除原来的元素，以原有配置重新生成一个元素，保证当前操作的元素始终在画布的最上层
      const d = deviceList.value[mouseKeyObjectIndex.value]
      deviceList.value.splice(mouseKeyObjectIndex.value,1)
      deviceList.value.push(d)
      //清除选中元素下标，初始化点击坐标，删除辅助线
      mouseKeyObjectIndex.value = null
      handleItemBeforeMoveX.value = 0
      handleItemBeforeMoveY.value = 0
      const auxiliaryLineIndex = deviceList.value.findIndex(d => d.type === 'auxiliaryLine')
      deviceList.value.splice(auxiliaryLineIndex,1)
      renderCanvas()
    }else if(canvasInfo.status === 3){//获取结束点，结束绘制连接线，结束点只能是元素上层锚点
      const mx = (e.offsetX - canvasInfo.canvasOffsetX) / canvasInfo.scaleCount
      const my = (e.offsetY - canvasInfo.canvasOffsetY) / canvasInfo.scaleCount
      const endClickPoints = pointList.value.find(item => Math.sqrt(((mx - item.x)**2 + (my - item.y)**2)) <= 3)
      if(endClickPoints){
        if(endClickPoints.type === 'OUT'){
          ElMessage.error('连线只能从元素顶部锚点结束')
          const index = deviceList.value.findIndex(d => d.id === createLineId.value)
          deviceList.value.splice(index,1)
          renderCanvas()
        }else if(endClickPoints.parentId === deviceList.value.find(d => d.id === createLineId.value).beginPointsParentId){
          ElMessage.error('开始锚点和结束锚点不能在一个元素上')
          const index = deviceList.value.findIndex(d => d.id === createLineId.value)
          deviceList.value.splice(index,1)
          renderCanvas()
        }else{
          const endPoint = deviceList.value.find(d => d.id === createLineId.value)
          const endPointIndex = deviceList.value.findIndex(d => d.id === createLineId.value)
          deviceList.value.splice(endPointIndex,1)
          endPoint.endPointsX = null
          endPoint.endPointsY = null
          endPoint.endPointsParentId = endClickPoints.parentId
          endPoint.endPointsIndex = endClickPoints.index
          deviceList.value.push(endPoint)
          renderCanvas()
        }
      }else{
        const index = deviceList.value.findIndex(d => d.id === createLineId.value)
        deviceList.value.splice(index,1)
        renderCanvas()
      }
    }
    canvasInfo.status = 0
    canvas.removeEventListener('mousemove', () => {})
    canvas.removeEventListener('mouseup', () => {})
  })
})

//拖拽新增对象
const handleMouseDrop = async (e) => {
  await drop(e,canvasInfo).then(() => {
    renderCanvas()
  })
}
//右键删除对象
const handleDeleteItem = () => {
  const deleteId = deviceList.value[mouseKeyObjectIndex.value].id
  const deleteArr = deviceList.value.filter(d => {
    //删除（元素本身，元素作为开始点，元素作为结束点）的对象
    return d.id === deleteId || d.endPointsParentId === deleteId || d.beginPointsParentId === deleteId
  })
  console.log(deleteArr);
  deviceList.value = deviceList.value.filter(d => !deleteArr.some(s => s.id === d.id))
  renderCanvas()
  handleResetRightKeyActive()
}

//编辑对象属性
const editFlag = ref(false)//连续点击右键标志
const itemObj = reactive({
  title:''
})
const resetItemObj = () => {
  itemObj.title = ''
}


//右键编辑对象
const handleSettingItem = () => {
  editFlag.value = true
  itemObj.title = deviceList.value[mouseKeyObjectIndex.value].extData.title
  contextMenuLeft.value = '99999px'
  contextMenuTop.value = '99999px'
}
//重置右键菜单功能
const handleResetRightKeyActive = () => {
  deviceList.value.forEach(d => d.active = false)//取消元素选中状态
  contextMenuLeft.value = '99999px'
  contextMenuTop.value = '99999px'
  mouseKeyObjectIndex.value = null
  resetItemObj()
}
const renderCanvas = () => {
  console.log('重新渲染',canvasInfo.status);
  const canvas = document.getElementById('canvas')
  canvas.width = canvasInfo.width
  ctx.value.setTransform(canvasInfo.scaleCount,0,0,canvasInfo.scaleCount,canvasInfo.canvasOffsetX,canvasInfo.canvasOffsetY)
  // eslint-disable-next-line no-unused-vars
  deviceList.value.forEach((d,idx) => {
    if(d.type === 'rect'){
      ctx.value.strokeStyle = d.active ? '#33FFFF' :'#222'
      ctx.value.strokeRect(d.x,d.y,d.w,d.h)
      d.points.forEach(point => {
        ctx.value.save()
        ctx.value.beginPath();
        ctx.value.strokeStyle = '#278cf7'
        ctx.value.fillStyle = '#ffffff'
        ctx.value.arc(point.x,point.y,3,0,Math.PI * 2)
        ctx.value.stroke();
        ctx.value.fill();
        ctx.value.restore()
        let title = d.extData.title
        ctx.value.font = '400 12px sans-serif'
        ctx.value.textAlign  = 'center'
        ctx.value.textBaseline  = 'middle'
        ctx.value.fillStyle = '#66666699'
        let metrics = ctx.value.measureText(title);
        let testWidth = metrics.width;
        if(testWidth > d.w){
          const count = Math.floor((d.w - 10) / 12)
          title = title.slice(0,count - 1) + '…'
        }
        ctx.value.fillText(title, d.x + d.w / 2 , d.y + d.h /2);
      })
    }else if(d.type === 'line'){
      let beginX = null,beginY = null, endX = null, endY = null
      const beginIndex = deviceList.value.findIndex(item => item.id === d.beginPointsParentId)
      beginX = deviceList.value[beginIndex].points[d.beginPointsIndex].x
      beginY = deviceList.value[beginIndex].points[d.beginPointsIndex].y
      if(d.endPointsX && d.endPointsY){
        endX = d.endPointsX
        endY = d.endPointsY
      }else if(d.endPointsParentId){
        const endIndex = deviceList.value.findIndex(item => item.id === d.endPointsParentId)
        endX = deviceList.value[endIndex].points[d.endPointsIndex].x
        endY = deviceList.value[endIndex].points[d.endPointsIndex].y
      }else {
        return
      }
      let middleY = 0
      if(endX < beginX){
        middleY = Number(parseFloat(String(beginY + ((endY - beginY) / d.footerPoint - 1) * (d.beginPointsIndex - d.headerPoint + 1))).toFixed(0))
      }else if(endX > beginX){
        middleY = Number(parseFloat(String(endY - ((endY - beginY) / d.footerPoint - 1) * (d.beginPointsIndex - d.headerPoint + 1))).toFixed(0))
      }else if(endX === beginX){
        middleY = Number(parseFloat(String((beginY + endY) / 2 )))
      }
      //给线段添加边界值，4个节点分成3个线段，右键时循环判断是否在边界上
      d.ranges = [
        {
          x1:beginX,
          y1:beginY,
          x2:beginX,
          y2:middleY
        },{
          x1:beginX,
          y1:middleY,
          x2:endX,
          y2:middleY
        },{
          x1:endX,
          y1:middleY,
          x2:endX,
          y2:endY
        }
      ]
      ctx.value.save()
      ctx.value.strokeStyle = d.active ? '#33FFFF' : '#00CC99'
      ctx.value.lineWidth = 2
      ctx.value.beginPath();
      ctx.value.moveTo(beginX,beginY)
      ctx.value.lineTo(beginX, middleY);
      ctx.value.lineTo(endX, middleY);
      ctx.value.lineTo(endX ,endY)
      ctx.value.lineJoin = 'round'
      ctx.value.stroke();
      ctx.value.closePath();
      ctx.value.restore()

      let title = d.extData.title
      ctx.value.font = '400 11px sans-serif'
      ctx.value.textAlign  = endX <= beginX ? 'start' : 'end'
      ctx.value.textBaseline  = 'bottom'
      ctx.value.fillStyle = '#66666699'
      ctx.value.fillText(title, endX , middleY);
    }else if(d.type === 'auxiliaryLine'){
      ctx.value.save()
      ctx.value.strokeStyle = deviceList.value.some((i,idx) => idx !== mouseKeyObjectIndex.value &&  i.type === 'rect' && i.y === d.beginY) ? '#00CC99' : '#00CC9933'
      ctx.value.lineWidth = 2
      ctx.value.setLineDash([3,2]);
      ctx.value.beginPath();
      ctx.value.moveTo(d.beginX,d.beginY)
      ctx.value.lineTo(d.endX ,d.endY)
      ctx.value.lineJoin = 'round'
      ctx.value.stroke();
      ctx.value.closePath();
      ctx.value.restore()
    }
  })
}
const {
  handleItemDragStart,
  handleItemDragend,
  createNewId,
  calculatePoints,
  calculateRanges,
  drop,
  dragover,
  deviceList,
  pointList
} = canvasProcessFun()

watch(() => itemObj, () => {
  if((mouseKeyObjectIndex.value || mouseKeyObjectIndex.value === 0) && editFlag.value){
    deviceList.value[mouseKeyObjectIndex.value].extData.title = itemObj.title
    renderCanvas()
  }
},{
  deep:true
})


const showList = () => {
  console.log(deviceList.value);
}
</script>

<style scoped lang="scss">
.container{
  width:100%;
  height:100vh;
  display: flex;
  flex-wrap: nowrap;
  //background: red;
  .canvasWrap{
    background: #f5f5f5;
    width:100%;
    height:100%;
    border-right:1px solid #f1f1f1;
    #canvas{
      width: v-bind('canvasWidth');
      height: v-bind('canvasHeight');
    }
  }
  .materialWrap{
    box-sizing: border-box;
    padding:10px;
    flex: 0 0 280px;
    width:280px;
    height:100%;
    display: flex;
    flex-direction: column;
    user-select: none;
    .baseTitle{
      margin-bottom: 10px;
      font-weight: bold;
    }
    &_up{
      width:100%;
      height:300px;
      &_list{
        &_item{
          width:80px;
          height:40px;
          border: 1px solid #ddd;
        }
      }
    }
    &_down{
      width: 100%;
      height:calc(100% - 300px);
    }
  }
  .dialogs{
    .contextmenu{
      position: fixed;
      top:v-bind('contextMenuTop');
      left:v-bind('contextMenuLeft');
      width:200px;
      padding:5px 0;
      background: #ffffff;
      border-radius: 8px;
      box-shadow: 0 2px 8px #00000026;
      &_item{
        padding:0 10px;
        height:32px;
        color: #000000;
        font-weight: 400;
        font-size: 14px;
        line-height: 32px;
        white-space: nowrap;
        cursor: pointer;
        &:hover{
          background: #f5f5f5;
        }
        &:not(:nth-last-child(1)){
          border-bottom: 1px solid #f0f0f0;
        }
      }
    }
  }
}
</style>